package com.platform.common.security.utils;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Sm2Util {
    private static final String BC_VALUE = "BC";
    private static final String EC_VALUE = "EC";
    private static final String SM3SM2_VALUE = "SM3withSM2";
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters;
    private static ECParameterSpec ecParameterSpec;
    private static final int RS_LEN = 32;

    static {
        ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
        ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }

    }

    public Sm2Util() {
    }

    public static byte[] sm2Encrypt(byte[] data, PublicKey key) {
        return changeC1C2C3ToC1C3C2(sm2EncryptOld(data, key));
    }

    public static byte[] sm2Decrypt(byte[] data, PrivateKey key) {
        return sm2DecryptOld(changeC1C3C2ToC1C2C3(data), key);
    }

    public static byte[] signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) {
        return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey));
    }

    public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature signer = Signature.getInstance("SM3withSM2", "BC");
            signer.setParameter(parameterSpec);
            signer.initSign(privateKey, new SecureRandom());
            signer.update(msg, 0, msg.length);
            byte[] sig = signer.sign();
            return sig;
        } catch (Exception var6) {
            throw new RuntimeException(var6);
        }
    }

    public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey);
    }

    public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) {
        try {
            SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId);
            Signature verifier = Signature.getInstance("SM3withSM2", "BC");
            verifier.setParameter(parameterSpec);
            verifier.initVerify(publicKey);
            verifier.update(msg, 0, msg.length);
            return verifier.verify(rs);
        } catch (Exception var6) {
            throw new RuntimeException(var6);
        }
    }

    public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
        boolean c3Len = true;
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len);
        System.arraycopy(c1c2c3, c1c2c3.length - 32, result, c1Len, 32);
        System.arraycopy(c1c2c3, c1Len, result, c1Len + 32, c1c2c3.length - c1Len - 32);
        return result;
    }

    public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
        boolean c3Len = true;
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len);
        System.arraycopy(c1c3c2, c1Len + 32, result, c1Len, c1c3c2.length - c1Len - 32);
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - 32, 32);
        return result;
    }

    public static byte[] sm2EncryptOld(byte[] data, PublicKey key) {
        BCECPublicKey localECPublicKey = (BCECPublicKey)key;
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException var6) {
            throw new RuntimeException(var6);
        }
    }

    public static byte[] sm2DecryptOld(byte[] data, PrivateKey key) {
        BCECPrivateKey localECPrivateKey = (BCECPrivateKey)key;
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);

        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException var6) {
            throw new RuntimeException(var6);
        }
    }

    public static byte[] sm4Encrypt(byte[] keyBytes, byte[] plain) {
        if (keyBytes.length != 16) {
            throw new RuntimeException("err key length");
        } else if (plain.length % 16 != 0) {
            throw new RuntimeException("err data length");
        } else {
            try {
                Key key = new SecretKeySpec(keyBytes, "SM4");
                Cipher out = Cipher.getInstance("SM4/ECB/NoPadding", "BC");
                out.init(1, key);
                return out.doFinal(plain);
            } catch (Exception var4) {
                throw new RuntimeException(var4);
            }
        }
    }

    public static byte[] sm4Decrypt(byte[] keyBytes, byte[] cipher) {
        if (keyBytes.length != 16) {
            throw new RuntimeException("err key length");
        } else if (cipher.length % 16 != 0) {
            throw new RuntimeException("err data length");
        } else {
            try {
                Key key = new SecretKeySpec(keyBytes, "SM4");
                Cipher in = Cipher.getInstance("SM4/ECB/NoPadding", "BC");
                in.init(2, key);
                return in.doFinal(cipher);
            } catch (Exception var4) {
                throw new RuntimeException(var4);
            }
        }
    }

    public static byte[] sm3(byte[] bytes) {
        SM3Digest sm3 = new SM3Digest();
        sm3.update(bytes, 0, bytes.length);
        byte[] result = new byte[sm3.getDigestSize()];
        sm3.doFinal(result, 0);
        return result;
    }

    private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
        byte[] rs = rOrS.toByteArray();
        if (rs.length == 32) {
            return rs;
        } else if (rs.length == 33 && rs[0] == 0) {
            return Arrays.copyOfRange(rs, 1, 33);
        } else if (rs.length < 32) {
            byte[] result = new byte[32];
            Arrays.fill(result, (byte)0);
            System.arraycopy(rs, 0, result, 32 - rs.length, rs.length);
            return result;
        } else {
            throw new RuntimeException("err rs: " + Hex.toHexString(rs));
        }
    }

    private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
        ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
        byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
        byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
        byte[] result = new byte[64];
        System.arraycopy(r, 0, result, 0, r.length);
        System.arraycopy(s, 0, result, 32, s.length);
        return result;
    }

    private static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
        if (sign.length != 64) {
            throw new RuntimeException("err rs. ");
        } else {
            BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, 32));
            BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, 32, 64));
            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(new ASN1Integer(r));
            v.add(new ASN1Integer(s));

            try {
                return (new DERSequence(v)).getEncoded("DER");
            } catch (IOException var5) {
                throw new RuntimeException(var5);
            }
        }
    }

    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
            kpGen.initialize(ecParameterSpec, new SecureRandom());
            KeyPair kp = kpGen.generateKeyPair();
            return kp;
        } catch (Exception var2) {
            throw new RuntimeException(var2);
        }
    }

    public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static BCECPublicKey getPublickeyFromECPoint(ECPoint point) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(point, ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static String getPublickeyStrFromECPoint(ECPoint point) {
        byte[] publicKeyByte = point.getEncoded(false);
        String publicKeyStr = Hex.toHexString(publicKeyByte);
        return publicKeyStr;
    }

    public static PublicKey getPublickeyFromX509File(File file) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
            FileInputStream in = new FileInputStream(file);
            X509Certificate x509 = (X509Certificate)cf.generateCertificate(in);
            return x509.getPublicKey();
        } catch (Exception var4) {
            throw new RuntimeException(var4);
        }
    }

    public static String encrypt(String data, String pubKey) {
        ECPoint ecPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(ecPoint, ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        byte[] byteResult;
        try {
            byte[] byteData = data.getBytes();
            byte[] temp = sm2Engine.processBlock(byteData, 0, byteData.length);
            byteResult = changeC1C2C3ToC1C3C2(temp);
        } catch (InvalidCipherTextException var8) {
            throw new RuntimeException(var8);
        }

        return Hex.toHexString(byteResult);
    }

    public static String decrypt(String encryptedData, String privateKey) {
        byte[] priKeyByte = Hex.decode(privateKey);
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(new BigInteger(1, priKeyByte), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);
        byte[] result = null;

        try {
            byte[] encryptDataByte = hexToByte(encryptedData);
            byte[] temp = changeC1C3C2ToC1C2C3(encryptDataByte);
            result = sm2Engine.processBlock(temp, 0, temp.length);
        } catch (Exception var8) {
            var8.printStackTrace();
        }

        return new String(result);
    }

    public static byte[] hexToByte(String hex) throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        } else {
            char[] arr = hex.toCharArray();
            byte[] b = new byte[hex.length() / 2];
            int i = 0;
            int j = 0;

            for(int l = hex.length(); i < l; ++j) {
                String swap = "" + arr[i++] + arr[i];
                int byteint = Integer.parseInt(swap, 16) & 255;
                b[j] = (new Integer(byteint)).byteValue();
                ++i;
            }

            return b;
        }
    }

    public static List<String> getStringKey(KeyPair keyPair) {
        PublicKey publicKey = keyPair.getPublic();
        BCECPublicKey localECPublicKEy = (BCECPublicKey)publicKey;
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKEy.getQ(), ecDomainParameters);
        byte[] publicKeyByte = ecPublicKeyParameters.getQ().getEncoded(false);
        String publicKeyStr = Hex.toHexString(publicKeyByte);
        PrivateKey privateKey = keyPair.getPrivate();
        BCECPrivateKey bcecPrivateKey = (BCECPrivateKey)privateKey;
        BigInteger privateKeyBI = bcecPrivateKey.getD();
        String privateKeyStr = privateKeyBI.toString(16);
        System.out.println("公钥： " + publicKeyStr);
        System.out.println("公钥X=" + localECPublicKEy.getQ().getXCoord());
        System.out.println("公钥Y=" + localECPublicKEy.getQ().getYCoord());
        System.out.println("私钥： " + privateKeyStr);
        List<String> keyPairList = new ArrayList(2);
        keyPairList.add(publicKeyStr);
        keyPairList.add(privateKeyStr);
        return keyPairList;
    }

    public static void exportPublicKey(ECPoint publicKey, String path) {
        File file = new File(path);

        try {
            if (!file.exists()) {
                file.createNewFile();
            }

            byte[] buffer = publicKey.getEncoded(false);
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(buffer);
            fos.close();
        } catch (IOException var5) {
            var5.printStackTrace();
        }

    }

    public static ECPoint importPublicKeyByFile(String path) {
        File file = new File(path);

        try {
            if (!file.exists()) {
                return null;
            } else {
                FileInputStream fis = new FileInputStream(file);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[16];

                int size;
                while((size = fis.read(buffer)) != -1) {
                    baos.write(buffer, 0, size);
                }

                fis.close();
                return x9ECParameters.getCurve().decodePoint(baos.toByteArray());
            }
        } catch (IOException var6) {
            var6.printStackTrace();
            return null;
        }
    }

    public static ECPoint importPublicKeyByString(String publicHexString) {
        try {
            byte[] key = Hex.decodeStrict(publicHexString);
            int size = key.length;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            baos.write(key, 0, size);
            return x9ECParameters.getCurve().decodePoint(baos.toByteArray());
        } catch (Exception var4) {
            var4.printStackTrace();
            return null;
        }
    }

    public void exportPrivateKey(BigInteger privateKey, String path) {
        File file = new File(path);

        try {
            if (!file.exists()) {
                file.createNewFile();
            }

            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(privateKey);
            oos.close();
        } catch (IOException var5) {
            var5.printStackTrace();
        }

    }

    public BigInteger importPrivateKey(String path) {
        File file = new File(path);

        try {
            if (!file.exists()) {
                return null;
            } else {
                FileInputStream fis = new FileInputStream(file);
                ObjectInputStream ois = new ObjectInputStream(fis);
                BigInteger res = (BigInteger)ois.readObject();
                ois.close();
                fis.close();
                return res;
            }
        } catch (Exception var6) {
            var6.printStackTrace();
            return null;
        }
    }

    public static List<String> makeSm2KeyForStringType() {
        KeyPair kp3 = generateKeyPair();
        List<String> listKeyPair = getStringKey(kp3);
        return listKeyPair;
    }

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, CertPathBuilderException, InvalidKeyException, SignatureException, CertificateException {
        String testData = "hello,world!你好！";
        List<String> listKeyPair = makeSm2KeyForStringType();
        String publicKeyStr = (String)listKeyPair.get(0);
        String privateKeyStr = (String)listKeyPair.get(1);
        System.out.println("公钥key=" + publicKeyStr);
        System.out.println("私钥key=" + privateKeyStr);
    }
}
